home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / TSR / TSRSRC35 / MEMU.PAS < prev    next >
Pascal/Delphi Source File  |  1993-10-21  |  30KB  |  1,039 lines

  1. {**************************************************************************
  2. *   MEMU - utility unit for TSR Utilities.                                *
  3. *   Copyright (c) 1991,1993 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. *                                                                         *
  6. *   Version 3.0 9/24/91                                                   *
  7. *     first release                                                       *
  8. *   Version 3.1 11/4/91                                                   *
  9. *     update for new WATCH identification behavior                        *
  10. *     update HasEnvironment for programs that shrink env size to 0        *
  11. *   Version 3.2 11/22/91                                                  *
  12. *     add FindHiMemStart function to generalize high memory access        *
  13. *     modify FindTheBlocks for new high memory approach                   *
  14. *     add MergeHiMemBlocks procedure to merge memory blocks in hi mem     *
  15. *     add ValidPsp function to determine whether a Psp still exists       *
  16. *   Version 3.3 1/8/92                                                    *
  17. *     add NextArg function to parse command lines more flexibly           *
  18. *   Version 3.4 2/14/92                                                   *
  19. *     change NextArg to ignore embedded '-'                               *
  20. *     change FindTheBlocks to support new /L switches in MAPMEM, DISABLE  *
  21. *     change StripNonAscii to allow European accented characters          *
  22. *   Version 3.5 10/11/93                                                  *
  23. *     change FindHiMemStart to use either the DOS UMB link or the old     *
  24. *       empirical method                                                  *
  25. *     add GetCDCount to get information about MSCDEX CD-ROMs used by      *
  26. *       MARKNET and RELNET                                                *
  27. ***************************************************************************}
  28.  
  29. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  30.  
  31. unit MemU;
  32.   {-Miscellaneous memory functions needed for TSR Utilities}
  33.  
  34. interface
  35.  
  36. const
  37.   {!!!!!! Following may change when WATCH reassembled. Check WATCH.MAP !!!!!}
  38.   ChangeVectors = $320;
  39.   OrigVectors = $720;
  40.  
  41.   {Offsets into resident copy of WATCH.COM for data storage}
  42.   WatchOfs = $80;             {Location of length of command line}
  43.   WatchOffset = $81;          {Location of start of command line}
  44.   NextChange = $104;          {Data structures within WATCH}
  45.   WatchId = 'TSR WATCHER';    {ID placed in WATCH command line}
  46.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  47.  
  48.   Version = '3.5';            {TSR Utilities version number}
  49.   MarkID  = 'MM3.5 TSR';      {Marking string for TSR MARK}
  50.   FmarkID = 'FM3.5 TSR';      {Marking string for TSR file mark}
  51.   NmarkID = 'MN3.5 TSR';      {Marking string for TSR file mark}
  52.   NetMarkID = 'MN35';         {ID at start of net mark file}
  53.  
  54.   {Offsets into resident mark copies for id strings}
  55.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  56.   FmarkOffset = $60;          {Where FmarkID is found in FMARK TSR}
  57.   NmarkOffset = $60;          {Where NmarkID is found in FMARK TSR}
  58.  
  59.   {Offsets into resident copy of MARK for data storage}
  60.   VectorOffset = $120;        {Where vector table is stored}
  61.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  62.   IntComOffset = $528;        {Where the interapps comm area is stored}
  63.   ParentOffset = $538;        {(TER) Where parent's PSP segment is stored}
  64.   ParLenOffset = $53A;        {Where parent's PSP mcb length is stored}
  65.   EMScntOffset = $53C;        {Where count of EMS active pages is stored}
  66.   EMSmapOffset = $53E;        {Where the page map is stored}
  67.  
  68. const
  69.   MaxBlocks = 256;            {Max number of DOS allocation blocks supported}
  70.  
  71.   ProtectChar = '!';          {Marks whose name begins with this will be
  72.                                released ONLY if an exact name match occurs}
  73.  
  74. const
  75.   RBR = 0; {Receiver buffer register offset}
  76.   THR = 0; {Transmitter buffer register offset}
  77.   BRL = 0; {Baud rate low}
  78.   BRH = 1; {Baud rate high}
  79.   IER = 1; {Interrupt enable register}
  80.   IIR = 2; {Interrupt identification register}
  81.   LCR = 3; {Line control register}
  82.   MCR = 4; {Modem control register}
  83.   LSR = 5; {Line status register}
  84.   MSR = 6; {Modem status register}
  85.  
  86. type
  87.   OS =
  88.     record
  89.       O, S : Word;
  90.     end;
  91.  
  92.   StringPtr = ^String;
  93.  
  94.   NameArray = array[1..8] of Char;
  95.  
  96.   McbPtr = ^Mcb;
  97.   Mcb =
  98.     record
  99.       Id : Char;
  100.       Psp : Word;
  101.       Len : Word;
  102.       Unused : array[1..3] of Byte;
  103.       Name : NameArray;
  104.     end;
  105.  
  106.   Block =
  107.   record                      {Store info about each memory block}
  108.     mcb : Word;
  109.     psp : Word;
  110.     releaseIt : Boolean;
  111.   end;
  112.  
  113.   BlockType = 0..MaxBlocks;
  114.   BlockArray = array[1..MaxBlocks] of Block;
  115.  
  116.   McbGroup =
  117.   record
  118.     Count : Word;
  119.     Mcbs : array[1..MaxBlocks] of
  120.            record
  121.              mcb : Word;
  122.              psp : Word;
  123.            end;
  124.   end;
  125.  
  126.   ChangeBlock =
  127.   record                      {Store info about each vector takeover}
  128.     VecNum : byte;
  129.     case ID : byte of
  130.       0, 1 : (VecOfs, VecSeg : Word);
  131.       2    : (SaveCode : array[1..6] of byte);
  132.       $FF  : (PspAdd : Word);
  133.   end;
  134.   {
  135.   ID is interpreted as follows:
  136.     00 = ChangeBlock holds the new pointer for vector vecnum
  137.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  138.     02 = ChangeBlock holds the code underneath the vector patch
  139.     FF = ChangeBlock holds the segment of a new PSP
  140.   }
  141.   ChangeArray = array[0..MaxChanges] of ChangeBlock;
  142.  
  143.   {Structure of a device driver header}
  144.   DeviceHeader =
  145.     record
  146.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  147.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  148.       Attributes : Word;          {Device attributes}
  149.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  150.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  151.       DeviceName : array[1..8] of Char; {Name of the device}
  152.     end;
  153.   DeviceHeaderPtr = ^DeviceHeader;
  154.   DeviceArray = array[1..256] of DeviceHeaderPtr;
  155.  
  156.   CDROMDeviceHeader =
  157.     record
  158.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  159.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  160.       Attributes : Word;          {Device attributes}
  161.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  162.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  163.       DeviceName : array[1..8] of Char; {Name of the device}
  164.       Reserved : Word;            {CD extensions}
  165.       DriveLet : Byte;
  166.       UnitCount : Byte;
  167.     end;
  168.   CDROMDeviceHeaderPtr = ^CDROMDeviceHeader;
  169.  
  170.   FileRec =
  171.     record
  172.       OpenCnt : Word;
  173.       OpenMode : Word;
  174.       Attribute : Byte;
  175.       Unknown1 : Word;
  176.       DCB : Pointer;
  177.       InitCluster : Word;
  178.       Time : Word;
  179.       Date : Word;
  180.       Size : LongInt;
  181.       Pos : LongInt;
  182.       BeginCluster : Word;
  183.       CurCluster : Word;
  184.       Block : Word;
  185.       Unknown2 : Byte;            {Varies with DOS version beyond here}
  186.       Name : array[0..7] of Char;
  187.       Ext : array[0..2] of Char;
  188.       Unknown3 : array[0..5] of Byte;
  189.       Owner : Word;
  190.       Unknown4 : Word;
  191.     end;
  192.  
  193.   SftRecPtr = ^SftRec;
  194.   SftRec =
  195.     record
  196.       Next : SftRecPtr;
  197.       Count : Word;
  198.       Files : array[1..255] of FileRec;
  199.     end;
  200.  
  201.   CurDirRec =
  202.     record
  203.       DrivePath : array[0..66] of Char;
  204.       Flags : Word;
  205.       DPB : Pointer;
  206.       RedirIfs : Pointer;
  207.       Param : Word;
  208.       BackSlashOfs : Word;
  209.       Dummy : array[1..7] of Byte; {Only for DOS 4.0+}
  210.     end;
  211.   CurDirRecPtr = ^CurDirRec;
  212.  
  213.   DosRec =
  214.     record
  215.       McbSeg : Word;
  216.       FirstDPB : Pointer;
  217.       FirstSFT : SftRecPtr;
  218.       ClockDriver : Pointer;
  219.       ConDriver : Pointer;
  220.       MaxBlockBytes : Word;
  221.       CachePtr : Pointer;
  222.       CurDirTable : CurDirRecPtr;
  223.       FcbTable : Pointer;
  224.       ProtectedFcbCount : Word;
  225.       BlockDevices : Byte;
  226.       LastDrive : Byte;
  227.       NullDevice : DeviceHeader;
  228.       JoinedDrives : Byte;           {Following valid DOS 4.0 or later}
  229.       SpecialProgOfs : Word;
  230.       IFSPtr : Pointer;
  231.       IFSList : Pointer;
  232.       BuffersX : Word;
  233.       BuffersY : Word;
  234.       BootDrive : Byte;
  235.       Unknown1 : Byte;
  236.       ExtMemSize : Word;
  237.     end;
  238.   DosRecPtr = ^DosRec;
  239.  
  240.   ComRec =  {State of the communications system}
  241.     record
  242.       Base : Word;
  243.       IERReg : Byte;
  244.       LCRReg : Byte;
  245.       MCRReg : Byte;
  246.       BRLReg : Byte;
  247.       BRHReg : Byte;
  248.     end;
  249.   ComArray = array[1..2] of ComRec;
  250.  
  251.   CDROMDeviceRec =
  252.     record
  253.       SubUnit : Byte;
  254.       Header : CDROMDeviceHeaderPtr;
  255.     end;
  256.   CDROMDeviceArray = array[1..26] of CDROMDeviceRec;
  257.  
  258. const
  259.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  260.   DosDelimSet : set of Char = ['\', ':', #0];
  261.  
  262. var
  263.   DosVM : Byte;      {Minor DOS version number}
  264.   DosV : Byte;       {Major DOS version number}
  265.   DosVT : Word absolute DosVM; {Combined version number}
  266.   DosList : Pointer; {Pointer to DOS list of lists}
  267.   Mcb1 : McbPtr;     {First MCB in system}
  268.  
  269. function GetDosListPtr : Pointer;
  270.   {-Return address of DOS list of lists}
  271.  
  272. function GetUmbLinkStatus : Boolean;
  273.   {-Return status of DOS 5 upper memory block link}
  274.  
  275. function SetUmbLinkStatus(On : Boolean) : Word;
  276.   {-Change state of DOS 5 upper memory block link}
  277.  
  278. function DosVersion : Word;
  279.   {-Return DOS version number with high byte = major version number}
  280.  
  281. function TopOfMemSeg : Word;
  282.   {-Return segment of top of normal memory}
  283.  
  284. function FindHiMemStart : word;
  285.   {-Return segment of first mcb in high memory, 0 if none}
  286.  
  287. procedure MergeHiMemBlocks(HiMemSeg : Word);
  288.   {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  289.  
  290. function HexB(B : Byte) : String;
  291.   {-Return hex string for byte}
  292.  
  293. function HexW(W : Word) : String;
  294.   {-Return hex string for word}
  295.  
  296. function HexPtr(P : Pointer) : string;
  297.   {-Return hex string for pointer}
  298.  
  299. function StUpcase(S : String) : String;
  300.   {-Return the uppercase string}
  301.  
  302. function JustFilename(PathName : String) : String;
  303.   {-Return just the filename of a pathname}
  304.  
  305. function JustName(PathName : String) : String;
  306.   {-Return just the name (no extension, no path) of a pathname}
  307.  
  308. function Extend(S : String; Len : Byte) : String;
  309.   {-Truncate or pad S to length Len}
  310.  
  311. function SmartExtend(S : String; Len : Byte) : String;
  312.   {-Truncate or pad S to length Len; end with '...' if truncated}
  313.  
  314. function Asc2Str(Name : NameArray) : String;
  315.   {-Convert array[1..8] of char to string}
  316.  
  317. procedure StripNonAscii(var S : String);
  318.   {-Return an empty string if input contains non-ASCII characters}
  319.  
  320. function CommaIze(L : LongInt; Width : Byte) : String;
  321.   {-Convert L to a string and add commas for thousands}
  322.  
  323. function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  324.   {-Return True if M has an associated environment block}
  325.  
  326. function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  327.   {-Return True if PspSeg is a valid, existing Psp}
  328.  
  329. function NameFromEnv(M : McbPtr) : String;
  330.   {-Return M's name from its environment (already known to exist)}
  331.  
  332. function NameFromMcb(M : McbPtr) : String;
  333.   {-Return name from the Mcb (DOS 4+ only)}
  334.  
  335. function MasterCommandSeg(HiMemSeg : Word) : Word;
  336.   {-Return PSP segment of master COMMAND.COM, searching high memory first}
  337.  
  338. function WatchPspSeg : Word;
  339.   {-Find copy of WATCH.COM in memory, returning its PSP segment or 0}
  340.  
  341. procedure FindTheBlocks(UseLoMem : Boolean;
  342.                         HiMemSeg : Word;
  343.                         var Blocks : BlockArray;
  344.                         var BlockMax : BlockType;
  345.                         var StartMcb : Word);
  346.   {-Scan memory for the allocated memory blocks}
  347.  
  348. procedure StuffKey(W : Word);
  349.   {-Stuff one key into the keyboard buffer}
  350.  
  351. procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  352.   {-Stuff up to 16 keys into keyboard buffer}
  353.  
  354. function ExistFile(path : String) : Boolean;
  355.   {-Return true if file exists}
  356.  
  357. function NextArg(S : String; var SPos : Word) : String;
  358.   {-Return next argument beginning at SPos in S.
  359.     Increment SPos to point past the argument.
  360.     Arguments are delimited by white space, and '/'.}
  361.  
  362. procedure IntsOff;
  363.   {-Turn off CPU interrupts}
  364. inline($FA);
  365.  
  366. procedure IntsOn;
  367.   {-Turn on CPU interrupts}
  368. inline($FB);
  369.  
  370. procedure NullJump;
  371.   {-Slight delay}
  372. inline($EB/$00);
  373.  
  374. function GetCDCount(var CDInfo : CDROMDeviceArray) : Word;
  375.   {-Return number of MSCDEX CD-ROMs and info about them}
  376.  
  377.   {=======================================================================}
  378.  
  379. implementation
  380.  
  381. uses
  382.   xms;
  383.  
  384.   function GetDosListPtr : Pointer; Assembler;
  385.     {-Return address of DOS list of lists}
  386.   asm
  387.     mov     ah,$52
  388.     int     $21
  389.     mov     dx,es
  390.     mov     ax,bx
  391.   end;
  392.  
  393.   function GetUmbLinkStatus : Boolean; Assembler;
  394.     {-Return status of DOS 5 upper memory block link}
  395.   asm
  396.     mov     ax,$5802
  397.     int     $21
  398.   end;
  399.  
  400.   function SetUmbLinkStatus(On : Boolean) : Word; Assembler;
  401.     {-Change state of DOS 5 upper memory block link}
  402.   asm
  403.     mov     ax,$5803
  404.     mov     bl,On
  405.     xor     bh,bh
  406.     int     $21
  407.     jc      @1
  408.     xor     ax,ax
  409. @1:
  410.   end;
  411.  
  412.   function DosVersion : Word; Assembler;
  413.     {-Return major DOS version number}
  414.   asm
  415.     mov     ah,$30
  416.     int     $21
  417.     xchg    ah,al
  418.   end;
  419.  
  420.   function TopOfMemSeg : Word;
  421.     {-Return segment of top of memory}
  422.   var
  423.     KBRAM : Word;
  424.   begin
  425.     asm
  426.       int $12
  427.       mov KBRAM,ax
  428.     end;
  429.     TopOfMemSeg := KBRAM shl 6;
  430.   end;
  431.  
  432.   function FindHiMemViaDosLink : word;
  433.     {-Return segment of first mcb in high memory, assuming DOS has linked it}
  434.   var
  435.     M : mcbptr;
  436.   begin
  437.     FindHiMemViaDosLink := 0;
  438.     M := Mcb1;
  439.     repeat
  440.       if OS(M).S > $9FFF then begin
  441.         FindHiMemViaDosLink := OS(M).S;
  442.         exit;
  443.       end;
  444.       if M^.ID = 'Z' then
  445.         exit;
  446.       M := Ptr(OS(M).S+M^.Len+1, 0);
  447.     until False;
  448.   end;
  449.  
  450.   function FindHiMemViaSearch : Word;
  451.     {-Find start of high memory using a search technique}
  452.   var
  453.     Mseg : word;
  454.     M : mcbptr;
  455.     N : mcbptr;
  456.     Done : boolean;
  457.     Invalid : boolean;
  458.   begin
  459.     Mseg := TopOfMemSeg;
  460.     Done := False;
  461.     repeat
  462.       M := Ptr(Mseg, 0);
  463.       case M^.Id of
  464.         'M' {, 'Z'} : {There must be at least 2 mcbs in high memory}
  465.           begin
  466.             {determine whether this is a valid chain of mcbs}
  467.             N := M;
  468.             Invalid := False;
  469.             repeat
  470.               case N^.Id of
  471.                 'M' :
  472.                   if LongInt(OS(N).S)+N^.Len+1 <= $FFFF then
  473.                     {next mcb won't land beyond $FFFF}
  474.                     N := Ptr(OS(N).S+N^.Len+1, 0)
  475.                   else
  476.                     Invalid := true;
  477.                 'Z' :
  478.                   begin
  479.                     {found end of chain starting at M}
  480.                     FindHiMemViaSearch := Mseg;
  481.                     Done := True;
  482.                   end;
  483.               else
  484.                 Invalid := True;
  485.               end;
  486.             until Done or Invalid;
  487.           end;
  488.       end;
  489.       if Mseg < $FFFF then
  490.         inc(Mseg)
  491.       else
  492.         Done := True;
  493.     until Done;
  494.   end;
  495.  
  496.   function FindHiMemStart : word;
  497.     {-Return segment of first mcb in high memory}
  498.   var
  499.     Segment : word;
  500.     Size : word;
  501.     Status : word;
  502.   begin
  503.     {assume failure}
  504.     FindHiMemStart := 0;
  505.  
  506.     {try to use the DOS link function}
  507.     if GetUmbLinkStatus then
  508.       {high memory already linked}
  509.       Segment := FindHiMemViaDosLink
  510.     else begin
  511.       {link high memory}
  512.       Status := SetUmbLinkStatus(True);
  513.       if Status = 0 then begin
  514.         Segment := FindHiMemViaDosLink;
  515.         Status := SetUmbLinkStatus(False);
  516.       end else
  517.         Segment := 0;
  518.     end;
  519.     if Segment <> 0 then begin
  520.       FindHiMemStart := Segment;
  521.       Exit;
  522.     end;
  523.  
  524.     {assure XMS driver installed}
  525.     if not XmsInstalled then
  526.       Exit;
  527.  
  528.     {confirm that UMBs can be created}
  529.     Status := AllocateUmbMem($FFFF, Segment, Size);
  530.     case status of
  531.       $B0, $B1 : ; {UMBs are possible, but not to allocate $FFFF paragraphs}
  532.     else
  533.       Exit;        {UMBs are not possible}
  534.     end;
  535.  
  536.     {use an empirical search}
  537.     FindHiMemStart := FindHiMemViaSearch;
  538.   end;
  539.  
  540.   procedure MergeHiMemBlocks(HiMemSeg : Word);
  541.     {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  542.   var
  543.     M : McbPtr;
  544.     N : McbPtr;
  545.     Done : Boolean;
  546.   begin
  547.     if HiMemSeg = 0 then
  548.       Exit;
  549.     M := Ptr(HiMemSeg, 0);
  550.     Done := False;
  551.     repeat
  552.       Done := (M^.Id = 'Z');
  553.       if not Done then begin
  554.         N := Ptr(OS(M).S+M^.Len+1, 0);
  555.         if (M^.Psp = 0) and (N^.Psp = 0) then begin
  556.           {This block and the next are both free}
  557.           inc(M^.Len, N^.Len+1);
  558.           M^.Id := N^.Id;
  559.         end else
  560.           M := N;
  561.       end;
  562.     until Done;
  563.   end;
  564.  
  565.   function HexB(B : Byte) : String;
  566.     {-Return hex string for byte}
  567.   begin
  568.     HexB[0] := #2;
  569.     HexB[1] := Digits[B shr 4];
  570.     HexB[2] := Digits[B and $F];
  571.   end;
  572.  
  573.   function HexW(W : Word) : String;
  574.     {-Return hex string for word}
  575.   begin
  576.     HexW[0] := #4;
  577.     HexW[1] := Digits[Hi(W) shr 4];
  578.     HexW[2] := Digits[Hi(W) and $F];
  579.     HexW[3] := Digits[Lo(W) shr 4];
  580.     HexW[4] := Digits[Lo(W) and $F];
  581.   end;
  582.  
  583.   function HexPtr(P : Pointer) : string;
  584.     {-Return hex string for pointer}
  585.   begin
  586.     HexPtr := HexW(OS(P).S)+':'+HexW(OS(P).O);
  587.   end;
  588.  
  589.   function StUpcase(s : String) : String;
  590.     {-Return the uppercase string}
  591.   var
  592.     i : Byte;
  593.   begin
  594.     for i := 1 to Length(s) do
  595.       s[i] := UpCase(s[i]);
  596.     StUpcase := s;
  597.   end;
  598.  
  599.   function JustFilename(PathName : String) : String;
  600.     {-Return just the filename of a pathname}
  601.   var
  602.     I : Word;
  603.   begin
  604.     I := Word(Length(PathName))+1;
  605.     repeat
  606.       Dec(I);
  607.     until (PathName[I] in DosDelimSet) or (I = 0);
  608.     JustFilename := Copy(PathName, I+1, 64);
  609.   end;
  610.  
  611.   function JustName(PathName : String) : String;
  612.     {-Return just the name (no extension, no path) of a pathname}
  613.   var
  614.     DotPos : Byte;
  615.   begin
  616.     PathName := JustFilename(PathName);
  617.     DotPos := Pos('.', PathName);
  618.     if DotPos > 0 then
  619.       PathName := Copy(PathName, 1, DotPos-1);
  620.     JustName := PathName;
  621.   end;
  622.  
  623.   function Extend(S : String; Len : Byte) : String;
  624.     {-Truncate or pad S to length Len}
  625.   begin
  626.     if Length(S) < Len then
  627.       FillChar(S[Length(S)+1], Len-Length(S), ' ');
  628.     S[0] := Char(Len);
  629.     Extend := S;
  630.   end;
  631.  
  632.   function SmartExtend(S : String; Len : Byte) : String;
  633.     {-Truncate or pad S to length Len; end with '...' if truncated}
  634.   begin
  635.     if Length(S) > Len then
  636.       SmartExtend := copy(S, 1, Len-3)+'...'
  637.     else
  638.       SmartExtend := Extend(S, Len);
  639.   end;
  640.  
  641.   function Asc2Str(Name : NameArray) : String;
  642.     {-Convert array[1..8] of char to string}
  643.   var
  644.     I : Integer;
  645.   begin
  646.     I := 1;
  647.     while (I <= 8) and (Name[I] <> #0) and (Name[I] <> ' ') do begin
  648.       Asc2Str[I] := Name[I];
  649.       Inc(I);
  650.     end;
  651.     Asc2Str[0] := Char(I-1);
  652.   end;
  653.  
  654.   procedure StripNonAscii(var S : String);
  655.     {-Return an empty string if input contains non-ASCII characters}
  656.   var
  657.     I : Integer;
  658.     Ok : Boolean;
  659.   begin
  660.     Ok := True;
  661.     I := 1;
  662.     while Ok and (I <= Length(S)) do begin
  663.       case S[I] of
  664.         #0..#31, #127, #166..#255 : Ok := False;
  665.       end;
  666.       Inc(I);
  667.     end;
  668.     if not Ok then
  669.       S := '';
  670.   end;
  671.  
  672.   function CommaIze(L : LongInt; Width : Byte) : String;
  673.     {-Convert L to a string and add commas for thousands}
  674.   var
  675.     I : Word;
  676.     Len : Word;
  677.     S : String[19];
  678.   begin
  679.     Str(L, S);
  680.     Len := Length(S);
  681.     I := Len;
  682.     while I > 1 do begin
  683.       if (Len+1-I) mod 3 = 0 then
  684.         insert(',', S, I);
  685.       dec(I);
  686.     end;
  687.     while Length(S) < Width do
  688.       insert(' ', S, 1);
  689.     CommaIze := S;
  690.   end;
  691.  
  692.   function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  693.     {-Return True if M has an associated environment block}
  694.   var
  695.     EnvSeg : Word;
  696.  
  697.     function HasEnv(Start : McbPtr) : Boolean;
  698.     var
  699.       N : McbPtr;
  700.       Done : Boolean;
  701.     begin
  702.       N := Start;
  703.       repeat
  704.         if (N^.Psp = M^.Psp) and (N^.Len > 0) and (EnvSeg = OS(N).S+1) then begin
  705.           HasEnv := True;
  706.           Exit;
  707.         end;
  708.         Done := (N^.Id = 'Z');
  709.         N := Ptr(OS(N).S+N^.Len+1, 0);
  710.       until Done;
  711.       HasEnv := False;
  712.     end;
  713.  
  714.   begin
  715.     EnvSeg := MemW[M^.Psp:$2C];
  716.     if HasEnv(Mcb1) then
  717.       HasEnvironment := True
  718.     else if (HiMemSeg <> 0) and HasEnv(Ptr(HiMemSeg, 0)) then
  719.       HasEnvironment := True
  720.     else
  721.       HasEnvironment := False;
  722.   end;
  723.  
  724.   function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  725.     {-Return True if PspSeg is a valid, existing Psp}
  726.  
  727.     function ValidP(Start : McbPtr) : Boolean;
  728.     var
  729.       N : McbPtr;
  730.       Done : Boolean;
  731.     begin
  732.       N := Start;
  733.       repeat
  734.         if (N^.Psp = PspSeg) and (N^.Len = PspLen) then begin
  735.           ValidP := True;
  736.           Exit;
  737.         end;
  738.         Done := (N^.Id = 'Z');
  739.         N := Ptr(OS(N).S+N^.Len+1, 0);
  740.       until Done;
  741.       ValidP := False;
  742.     end;
  743.  
  744.   begin
  745.     if ValidP(Mcb1) then
  746.       ValidPsp := True
  747.     else if (HiMemSeg <> 0) and ValidP(Ptr(HiMemSeg, 0)) then
  748.       ValidPsp := True
  749.     else
  750.       ValidPsp := False;
  751.   end;
  752.  
  753.   function NameFromEnv(M : McbPtr) : String;
  754.     {-Return M's name from its environment (already known to exist)}
  755.   type
  756.     CharArray = array[0..32767] of Char;
  757.     CharArrayPtr = ^CharArray;
  758.   var
  759.     E : Word;
  760.     Eptr : CharArrayPtr;
  761.     Name : String[79];
  762.     Nlen : Byte absolute Name;
  763.   begin
  764.     Eptr := Ptr(MemW[M^.Psp:$2C], 0);
  765.     E := 0;
  766.     repeat
  767.       if Eptr^[E] = #0 then begin
  768.         Inc(E);
  769.         if Eptr^[E] = #0 then begin
  770.           {found end of environment}
  771.           Inc(E, 3);
  772.           Nlen := 0;
  773.           while (Nlen < 63) and (Eptr^[E] <> #0) do begin
  774.             Inc(Nlen);
  775.             Name[Nlen] := Eptr^[E];
  776.             Inc(E);
  777.           end;
  778.           StripNonAscii(Name);
  779.           NameFromEnv := JustName(Name);
  780.           Exit;
  781.         end;
  782.       end;
  783.       Inc(E);
  784.     until (E > 32767);
  785.     NameFromEnv := '';
  786.   end;
  787.  
  788.   function NameFromMcb(M : McbPtr) : String;
  789.     {-Return name from the Mcb (DOS 4+ only)}
  790.   var
  791.     Name : String[79];
  792.   begin
  793.     Name := Asc2Str(M^.Name);
  794.     StripNonAscii(Name);
  795.     NameFromMcb := Name;
  796.   end;
  797.  
  798.   function MasterCommandSeg(HiMemSeg : Word) : Word;
  799.     {-Return PSP segment of master COMMAND.COM in low memory}
  800.   var
  801.     MCS : Word;
  802.  
  803.     function MasterCommandS(Start : McbPtr) : Word;
  804.     var
  805.       N : McbPtr;
  806.       Done : Boolean;
  807.     begin
  808.       N := Start;
  809.       repeat
  810.         if (OS(N).S+1 = N^.Psp) and (MemW[N^.Psp:$16] = N^.Psp) then begin
  811.           MasterCommandS := N^.Psp;
  812.           Exit;
  813.         end;
  814.         Done := (N^.Id = 'Z');
  815.         N := Ptr(OS(N).S+N^.Len+1, 0);
  816.       until Done;
  817.       MasterCommandS := 0;
  818.     end;
  819.  
  820.   begin
  821.     MCS := 0;
  822.     if HiMemSeg <> 0 then
  823.       MCS := MasterCommandS(Ptr(HiMemSeg, 0));
  824.     if MCS = 0 then
  825.       MCS := MasterCommandS(MCB1);
  826.     MasterCommandSeg := MCS;
  827.   end;
  828.  
  829.   function WatchPspSeg : Word; assembler;
  830.     {-Find copy of WATCH.COM in memory, returning its PSP segment or zero}
  831.   asm
  832.     mov ax,$7761     {id call to WATCH}
  833.     int $21
  834.     jc @1
  835.     cmp ax,$6177     {WATCH flips ah and al if installed}
  836.     jne @1
  837.     mov ax,bx        {WATCH returns its own CS in BX}
  838.     jmp @2
  839. @1: xor ax,ax        {not installed}
  840. @2:
  841.   end;
  842.  
  843.   procedure FindTheBlocks(UseLoMem : Boolean;
  844.                           HiMemSeg : Word;
  845.                           var Blocks : BlockArray;
  846.                           var BlockMax : BlockType;
  847.                           var StartMcb : Word);
  848.     {-Scan memory for the allocated memory blocks}
  849.   const
  850.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  851.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  852.   var
  853.     mcbSeg : Word;            {Segment address of current MCB}
  854.     nextSeg : Word;           {Computed segment address for the next MCB}
  855.     gotFirst : Boolean;       {True after first MCB is found}
  856.     gotLast : Boolean;        {True after last MCB is found}
  857.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  858.  
  859.     procedure StoreTheBlock(SaveBlock : Boolean;
  860.                             var mcbSeg, nextSeg : Word;
  861.                             var gotFirst, gotLast : Boolean);
  862.       {-Store information regarding the memory block}
  863.     var
  864.       nextID : Byte;
  865.       PspAdd : Word;       {Segment address of the current PSP}
  866.       mcbLen : Word;       {Size of the current memory block in paragraphs}
  867.  
  868.     begin
  869.  
  870.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  871.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  872.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  873.       nextID := Mem[nextSeg:0];
  874.  
  875.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then
  876.         if BlockMax < MaxBlocks then begin
  877.           gotFirst := True;
  878.           if SaveBlock then begin
  879.             inc(BlockMax);
  880.             with Blocks[BlockMax] do begin
  881.               mcb := mcbSeg;
  882.               psp := PspAdd;
  883.             end;
  884.           end;
  885.         end;
  886.     end;
  887.  
  888.     procedure ScanBlocks(SaveBlock : Boolean);
  889.       {-Scan memory until ending block is found}
  890.     begin
  891.       repeat
  892.         idbyte := Mem[mcbSeg:0];
  893.         if idbyte = MidBlockID then begin
  894.           StoreTheBlock(SaveBlock, mcbSeg, nextSeg, gotFirst, gotLast);
  895.           if gotFirst then
  896.             mcbSeg := nextSeg
  897.           else
  898.             inc(mcbSeg);
  899.         end else if gotFirst and (idbyte = EndBlockID) then begin
  900.           gotLast := True;
  901.           StoreTheBlock(SaveBlock, mcbSeg, nextSeg, gotFirst, gotLast);
  902.         end else
  903.           {Start block was invalid}
  904.           gotLast := True;
  905.       until gotLast;
  906.     end;
  907.  
  908.   begin
  909.     BlockMax := 0;
  910.     StartMCB := OS(MCB1).S;
  911.  
  912.     mcbSeg := StartMCB;
  913.     gotFirst := False;
  914.     gotLast := False;
  915.     ScanBlocks(UseLoMem);
  916.  
  917.     if HiMemSeg <> 0 then begin
  918.       mcbSeg := HiMemSeg;
  919.       gotFirst := False;
  920.       gotLast := False;
  921.       ScanBlocks(True);
  922.     end;
  923.   end;
  924.  
  925.   const
  926.     KbdStart = $1E;
  927.     KbdEnd = $3C;
  928.   var
  929.     KbdHead : Word absolute $40 : $1A;
  930.     KbdTail : Word absolute $40 : $1C;
  931.  
  932.   procedure StuffKey(W : Word);
  933.     {-Stuff one key into the keyboard buffer}
  934.   var
  935.     SaveKbdTail : Word;
  936.   begin
  937.     SaveKbdTail := KbdTail;
  938.     if KbdTail = KbdEnd then
  939.       KbdTail := KbdStart
  940.     else
  941.       Inc(KbdTail, 2);
  942.     if KbdTail = KbdHead then
  943.       KbdTail := SaveKbdTail
  944.     else
  945.       MemW[$40:SaveKbdTail] := W;
  946.   end;
  947.  
  948.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  949.     {-Stuff up to 16 keys into keyboard buffer}
  950.   var
  951.     Len : Byte;
  952.     I : Byte;
  953.   begin
  954.     if ClearFirst then
  955.       KbdTail := KbdHead;
  956.     Len := Length(Keys);
  957.     if Len > 16 then
  958.       Len := 16;
  959.     for I := 1 to Length(Keys) do
  960.       StuffKey(Ord(Keys[I]));
  961.   end;
  962.  
  963.   function ExistFile(path : String) : Boolean;
  964.     {-Return true if file exists}
  965.   var
  966.     F : file;
  967.   begin
  968.     Assign(F, path);
  969.     Reset(F);
  970.     if IoResult = 0 then begin
  971.       ExistFile := True;
  972.       Close(F);
  973.     end else
  974.       ExistFile := False;
  975.   end;
  976.  
  977.   function NextArg(S : String; var SPos : Word) : String;
  978.     {-Return next argument beginning at SPos in S.
  979.       Increment SPos to point past the argument.
  980.       Arguments are delimited by white space and '/'}
  981.   var
  982.     Start : Word;
  983.  
  984.     function Delimiter(Ch : Char) : Boolean;
  985.     begin
  986.       case Ch of
  987.         #0..' ', '/' : Delimiter := True;
  988.       else
  989.         Delimiter := False;
  990.       end;
  991.     end;
  992.  
  993.   begin
  994.     {Skip leading white space}
  995.     while (SPos <= Length(S)) and (S[SPos] <= ' ') do
  996.       inc(SPos);
  997.  
  998.     {Exit if beyond end of string}
  999.     if SPos > Length(S) then begin
  1000.       NextArg := '';
  1001.       Exit;
  1002.     end;
  1003.  
  1004.     {Find end of this argument}
  1005.     Start := SPos;
  1006.     inc(SPos);
  1007.     while (SPos <= Length(S)) and not Delimiter(S[Spos]) do
  1008.       inc(SPos);
  1009.  
  1010.     {Return the string}
  1011.     NextArg := Copy(S, Start, SPos-Start);
  1012.   end;
  1013.  
  1014. function GetCDCount(var CDInfo : CDROMDeviceArray) : Word; Assembler;
  1015.   {-Return number of MSCDEX CD-ROMs and info about them}
  1016. asm
  1017.   xor bx,bx
  1018.   mov ax,$1500
  1019.   int $2F
  1020.   mov ax,bx
  1021.   or ax,ax
  1022.   jz @1
  1023.   push ax
  1024.   mov ax,$1501
  1025.   les bx,CDInfo
  1026.   int $2F
  1027.   pop ax
  1028. @1:
  1029. end;
  1030.  
  1031. begin
  1032.   DosVT := DosVersion;
  1033.   DosList := GetDosListPtr;     {pointer to dos list of lists}
  1034.   Mcb1 := Ptr(MemW[OS(DosList).S:OS(DosList).O-2], 0); {first Mcb}
  1035. end.
  1036.  
  1037.  
  1038.  
  1039.